local addon = LibStub("AceAddon-3.0"):GetAddon("Poppins")

local prototype = addon:CreateClass("CheckButton")
addon.Base = prototype

function prototype:Initialize()
	self:SetWidth(36)
	self:SetHeight(36)
	self:RegisterForClicks("AnyUp")
	self:RegisterForDrag("Any")
	self:SetScript("OnEvent", self.OnEvent)

	local name = self:GetName()
	self.icon = self.icon or _G[format("%sIcon", name)]
	self.border = self.border or _G[format("%sBorder", name)]
	self.cooldown = self.cooldown or _G[format("%sCooldown", name)]
	self.macroName = self.macroName or _G[format("%sName", name)]
	self.hotkey = self.hotkey or self:CreateFontString(format("%sHotKey", name), "ARTWORK", "NumberFontNormal") -- _G[format("%sHotKey", name)]
	self.id = self.id or self:CreateFontString(format("%sID", name), "ARTWORK", "NumberFontNormal")
	self.count = self.count or _G[format("%sCount", name)]
	self.flash = self.falsh or _G[format("%sFlash", name)]
	self.flash:Hide()
end

function prototype:Reset()
	self:UnregisterAllEvents()

	self.rangeTimer = nil
	self.outOfRange = nil
	self.bagUpdate = nil
	self.flashing = nil
	self.flashtime = nil

	self.icon:SetTexture(nil)
	self.border:Hide()
	self.cooldown:Hide()
	self.macroName:SetText(nil)
	self.hotkey:SetText(nil)
	self.id:SetText(nil)
	self.count:SetText(nil)
	self.flash:Hide()

	self.UpdateTooltip = nil

	self:ClearAllPoints()
	self:Hide()
end

prototype.events = {
	-- Shared
	MODIFIER_STATE_CHANGED = true,
	UPDATE_BINDINGS = true,
	PLAYER_ENTERING_WORLD = true,
	-- Spell
	PLAYER_ENTER_COMBAT = true,
	PLAYER_LEAVE_COMBAT = true,
	SPELL_UPDATE_COOLDOWN = true,
	SPELL_UPDATE_USABLE = true,
	START_AUTOREPEAT_SPELL = true,
	STOP_AUTOREPEAT_SPELL = true,
	PLAYER_TARGET_CHANGED = true,
	UNIT_SPELLCAST_SENT = true,
	UNIT_SPELLCAST_STOP = true,
	UNIT_SPELLCAST_FAILED = true,
	UNIT_SPELLCAST_INTERRUPTED = true,
	CURSOR_UPDATE = true,
	CRAFT_SHOW = true,
	CRAFT_CLOSE = true,
	TRADE_SKILL_SHOW = true,
	TRADE_SKILL_CLOSE = true,
	-- Item
	BAG_UPDATE = true,
	BAG_UPDATE_COOLDOWN = true,
	UNIT_INVENTORY_CHANGED = true,
}

function prototype:RegisterAllEvents()
	self:UnregisterAllEvents()
	for event, register in pairs(self.events) do
		if register then
			self:RegisterEvent(event)
		end
	end
end

function prototype:OnEvent(event, ...)
	self[event](self, ...)
end

function prototype:OnShow()
	self:RegisterAllEvents()
	self:Update()
end

function prototype:OnHide()
	self:UnregisterAllEvents()
end

function prototype:OnEnter()
	local tooltips = addon.db.profile.tooltips
	if tooltips == 2 or (tooltips == 3 and not InCombatLockdown()) then
		self:ShowTooltip()
	else
		self:HideTooltip()
	end
end

function prototype:OnLeave()
	self:HideTooltip()
end

function prototype:OnClick()
	self:UpdateState()
end

local TOOLTIP_UPDATE_TIME = TOOLTIP_UPDATE_TIME
local ATTACK_BUTTON_FLASH_TIME = ATTACK_BUTTON_FLASH_TIME
function prototype:OnUpdate(elapsed)
	if self.flashing == 1 then
		self.flashtime = self.flashtime - elapsed
		if self.flashtime <= 0 then
			local overtime = -self.flashtime
			if overtime >= ATTACK_BUTTON_FLASH_TIME then
				overtime = 0
			end
			self.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime
			
			local flashTexture = self.flash
			if flashTexture:IsVisible() then
				flashTexture:Hide()
			else
				flashTexture:Show()
			end
		end
	end

	if self.rangeTimer then
		self.rangeTimer = self.rangeTimer - elapsed
		if self.rangeTimer <= 0 then
			local valid
			local kind, value = self.kind, self.value
			if kind == "spell" then
				valid = IsSpellInRange(value)
			elseif kind == "item" then
				valid = IsItemInRange(value)
			end
			self.outOfRange = (valid == 0)
			self:UpdateUsable()
			self.rangeTimer = TOOLTIP_UPDATE_TIME
		end
	end

	if self.bagUpdate then
		self.bagUpdate = self.bagUpdate - elapsed
		if self.bagUpdate <= 0 then
			self:UpdateCount()
			self:UpdateUsable(true)
			self.bagUpdate = nil
		end
	end
end

function prototype:ShowTooltip()
	if GetCVar("UberTooltips") == "1" then
		GameTooltip_SetDefaultAnchor(GameTooltip, self)
	else
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
	end

	local link, _
	local kind, value = self.kind, self.value
	if kind == "spell" and value then
		link = GetSpellLink(value)
	elseif kind == "item" and value then
		_, link = GetItemInfo(value)
	end
	if link and GameTooltip:SetHyperlink(link) then
		self.UpdateTooltip = self.ShowTooltip
	else
		self.UpdateTooltip = nil
	end
end

function prototype:HideTooltip()
	if GameTooltip:GetOwner() == self then
		GameTooltip:Hide()
	end
end

function prototype:GetAction(modifier)
	local value

	-- Get the effective button
	local button = SecureButton_GetEffectiveButton(self)

	-- Lookup the unit, based on the modifiers and button
	local unit = SecureButton_GetModifiedUnit(self, button)
 
	-- Remap button suffixes based on the disposition of the unit (contributed by Iriel and Cladhaire)
	if unit then
		local origButton = button
		if UnitCanAttack("player", unit) then
			button = SecureButton_GetModifiedAttribute(self, "harmbutton", button, modifier) or button
		elseif UnitCanAssist("player", unit) then
			button = SecureButton_GetModifiedAttribute(self, "helpbutton", button, modifier) or button
		end

		-- The unit may have changed based on button remapping
		if button ~= origButton then
			unit = SecureButton_GetModifiedUnit(self, button)
		end
	end

	-- Lookup the action type, based on the modifiers and button
	local kind = SecureButton_GetModifiedAttribute(self, "type", button, modifier)

	-- Get the requested action
	if kind == "action" then
		value = ActionButton_CalculateAction(self)
	elseif kind == "pet" then
		value = SecureButton_GetModifiedAttribute(self, "action", button, modifier)
	elseif kind == "spell" then
		value = SecureButton_GetModifiedAttribute(self, "spell", button, modifier)
	elseif kind == "item" then
		value = SecureButton_GetModifiedAttribute(self, "item", button, modifier)
	elseif kind == "macro" then
		value = SecureButton_GetModifiedAttribute(self, "macro", button, modifier) or SecureButton_GetModifiedAttribute(self, "macrotext", button, modifier)
	elseif kind == "click" then
		local delegate = SecureButton_GetModifiedAttribute(self, "clickbutton", button, modifier)
		if delegate then
			return self.GetAction(delegate)
		end
	end

	if kind and value then
		return kind, value, unit
	end
end

function prototype:Update()
	local kind, value = self:GetAction()
	local changed
	if kind ~= self.kind or value ~= self.value then
		self.kind, self.value = kind, value
		changed = true
	end

	self:UpdateIcon()
	self:UpdateCount()
	self:UpdateHotkeys()
	self:UpdateSelected()
	if value then
		if kind == "item" and IsEquippedItem(value) then
			self.border:SetVertexColor(0, 1.0, 0, 0.75)
			self.border:Show()
		else
			self.border:Hide()
		end
		self:UpdateState()
		self:UpdateUsable(true)
		self:UpdateCooldown()
		self:UpdateFlash()
		self:SetScript("OnUpdate", self.OnUpdate)
	else
		self.border:Hide()
		self.cooldown:Hide()
		self:SetScript("OnUpdate", nil)
	end

	if changed then
		--self.callbacks:Fire("OnUpdate", self, kind, value)
	end
end

function prototype:UpdateIcon()
	local icon, _
	local kind, value = self.kind, self.value
	if kind and value then
		if kind == "spell" then
			icon = GetSpellTexture(value)
		elseif kind == "item" then
			icon = GetItemIcon(value)
		elseif type == "macro" then
			_, icon = GetMacroInfo(value)
		end
		icon = icon or [[Interface\Icons\INV_Misc_QuestionMark]]
	end

	if icon then
		self.rangeTimer = -1
		self.icon:SetTexture(icon)
		self.icon:Show()
	else
		self.rangeTimer = nil
		self.icon:Hide()
		self.cooldown:Hide()
	end
end

function prototype:UpdateCount()
	local count

	local kind, value = self.kind, self.value
	if value then
		if kind == "spell" and IsConsumableSpell(value) then
			count = GetSpellCount(value)
		elseif kind == "item" and IsConsumableItem(value) then
			count = GetItemCount(value, false, true)
		elseif kind == "macro" then
			count = GetMacroInfo(value)
		end
	end
	self.count:SetText(count)
end

function prototype:UpdateState()
	local checked = 0
	local kind, value = self.kind, self.value
	if value then
		if kind == "spell" then
			checked = (IsCurrentSpell(value) or IsAutoRepeatSpell(value)) and 1 or 0
		elseif kind == "item" then
			checked = IsCurrentItem(value) and 1 or 0
		end
	end
	self:SetChecked(checked)
end

function prototype:UpdateUsable(force)
	local isUsable, notEnoughMana
	local icon, hotkey = self.icon, self.hotkey
	if force then 
		icon.state, hotkey.state = 1, 1 
	end

	if self.outOfRange then
		icon:SetVertexColor(0.8, 0.1, 0.1)
	else
		local kind, value = self.kind, self.value
		if value then
			if kind == "spell" then
				isUsable, notEnoughMana = IsUsableSpell(value)
			elseif kind == "item" then
				isUsable, notEnoughMana = IsUsableItem(value)
			end
		end

		if isUsable then
			icon:SetVertexColor(1.0, 1.0, 1.0)
		elseif notEnoughMana then
			icon:SetVertexColor(0.5, 0.5, 1.0)
		else
			icon:SetVertexColor(0.4, 0.4, 0.4)
		end
	end
end

function prototype:UpdateCooldown()
	local start, duration, enable

	local kind, value = self.kind, self.value
	if value then
		if kind == "spell" then
			start, duration, enable = GetSpellCooldown(value)
		elseif kind == "item" then
			start, duration, enable = GetItemCooldown(value)
		end
	end
	start, duration, enable = start or 0, duration or 0, enable or 0

	CooldownFrame_SetTimer(self.cooldown, start, duration, enable)
end

function prototype:UpdateFlash()
	local kind, value = self.kind, self.value
	if kind == "spell" and value and (IsAttackSpell(value) and IsCurrentSpell(value)) or IsAutoRepeatSpell(value) then
		self:StartFlash()
	else
		self:StopFlash()
	end
end

function prototype:StartFlash()
	local kind, value = self.kind, self.value
	if kind == "spell" and value and IsAttackSpell(value) then
		self.flashing = 1
		self.flashtime = 0
		self:UpdateState()
	end
end

function prototype:StopFlash()
	self.flashing = 0
	self.flash:Hide()
	self:UpdateState()
end

function prototype:UpdateHotkeys()
	local keybinding = self:GetHotkey() or ""
	local hotkey = self.hotkey

	if keybinding == "" then
		hotkey:SetText("")
	else
		hotkey:SetText(LibKeyBound:ToShortKey(keybinding))
	end
end

function prototype:GetHotkey()
end

function prototype:UpdateSelected()
	self.id:SetText(self:GetID())
end

function prototype:UPDATE_BINDINGS()
	self:UpdateHotkeys()
end

function prototype:MODIFIER_STATE_CHANGED(modifier, state)
	self:Update()
end

function prototype:BAG_UPDATE()
	local kind, value = self.kind, self.value
	if kind == "item" and value and not self.bagUpdate then
		self.bagUpdate = 0.1
	end
end

function prototype:BAG_UPDATE_COOLDOWN()
	local kind, value = self.kind, self.value
	if kind == "item" and value then
		self:UpdateCooldown()
	end
end

function prototype:UNIT_INVENTORY_CHANGED(event, unit)
	if unit ~= "player" then return end
	local kind, value = self.kind, self.value
	if kind == "item" and value then
		self:Update()
	end
end

function prototype:UNIT_SPELLCAST_SENT(event, unit, spell, rank)
	if unit ~= "player" then return end
	self:UpdateState()
end
prototype.UNIT_SPELLCAST_STOP = prototype.UNIT_SPELLCAST_SENT
prototype.UNIT_SPELLCAST_FAILED = prototype.UNIT_SPELLCAST_SENT
prototype.UNIT_SPELLCAST_INTERRUPTED = prototype.UNIT_SPELLCAST_SENT

function prototype:PLAYER_ENTER_COMBAT()
	self:StartFlash()
end

function prototype:PLAYER_LEAVE_COMBAT()
	self:StopFlash()
end

function prototype:SPELL_UPDATE_COOLDOWN()
	local kind, value = self.kind, self.value
	if kind == "spell" and value then
		self:UpdateCooldown()
	end
end

function prototype:SPELL_UPDATE_USABLE()
	local kind, value = self.kind, self.value
	if kind == "spell" and value then
		self:UpdateUsable()
	end
end

function prototype:START_AUTOREPEAT_SPELL()
	local kind, value = self.kind, self.value
	if kind == "spell" and value and IsAutoRepeatSpell(value) then
		self:StartFlash()
	end
end

function prototype:STOP_AUTOREPEAT_SPELL()
	local kind, value = self.kind, self.value
	if kind == "spell" and value and self.flashing == 1 and not IsAttackSpell(value) then
		self:StopFlash()
	end
end

function prototype:PLAYER_ENTERING_WORLD()
	self:Update()
end

function prototype:PLAYER_TARGET_CHANGED()
	self.rangeTimer = -1
end

function prototype:CRAFT_SHOW()
	self:UpdateState()
end
prototype.CRAFT_CLOSE = prototype.CRAFT_SHOW
prototype.TRADE_SKILL_SHOW = prototype.CRAFT_SHOW
prototype.TRADE_SKILL_CLOSE = prototype.CRAFT_SHOW

function prototype:CURSOR_UPDATE()
	self:UpdateState()
end